// #![allow(unused)]

use std::collections::HashMap;

use cgmath::{Vector2, Zero};
use image::Rgba;
use minifb::Key;

use crate::{HEIGHT, TEXTURES, WIDTH};
use crate::camera::*;
use crate::util::*;
use crate::renderer::*;

#[derive(Clone, Copy)]
#[derive(Hash, PartialEq, Eq)]
pub enum State {
    Stand,
    Crouch,
    Jump,
    Dash,
    Attack,
    Fail,
}

impl State {
    fn lasting_time(&self) -> f64 {
        use State::*;
        match self {
            Stand => f64::INFINITY,
            Crouch => f64::INFINITY,
            Jump => f64::INFINITY,
            Dash => 0.15,
            Attack => 0.25,
            Fail => f64::INFINITY,
        }
    }
}

pub struct Dinosaur {
    is_invincible: bool,
    invincible_timer: f64,
    current_health: i64,
    health_max: i64,
    health_recovery_timer: f64,

    current_energy: i64,
    energy_max: i64,
    energy_recovery_timer: f64,

    already_spit: bool,

    already_jump: bool,
    more_jump: bool,
    jump_timer: f64,

    pic_size: Vector2<f64>,
    position: Vector2<f64>,
    velocity: Vector2<f64>,

    current_state: State,
    state_timer: f64,

    textures: HashMap<String, (Texture, f64, String)>,
    animation_timer: f64,
    current_texture: String,
    to_play_hurt_animation: bool,

    collision_boxes: HashMap<State, (Vector2<f64>, Vector2<f64>)>,
}

impl Dinosaur {
    pub fn new() -> Self {
        Self {
            is_invincible: false,
            invincible_timer: 0.0,
            current_health: 3,
            health_max: 3,
            health_recovery_timer: 60.0,

            current_energy: 1,
            energy_max: 3,
            energy_recovery_timer: 5.0,

            already_spit: false,

            already_jump: false,
            more_jump: true,
            jump_timer: 0.3,

            pic_size: Vector2::new(100.0, 100.0),
            position: Vector2::zero(),
            velocity: Vector2::zero(),

            current_state: State::Stand,
            state_timer: State::Stand.lasting_time(),

            textures: HashMap::new(),
            animation_timer: 0.5,
            current_texture: "".into(),
            to_play_hurt_animation: false,

            collision_boxes: HashMap::new(),
        }
    }

    pub fn initialize(
        &mut self,
    ) {
        let texture_names: Vec<String> = vec![
            "Stand1".into(),
            "Stand2".into(),
            "Crouch1".into(),
            "Crouch2".into(),
            "Attack1".into(),
            "Attack2".into(),
            "Fireball".into(),
            "Fail".into(),
        ];
        let texture_infos = vec![
            (&texture_names[0], 0.1, &texture_names[1]),
            (&texture_names[1], 0.1, &texture_names[0]),
            (&texture_names[2], 0.1, &texture_names[3]),
            (&texture_names[3], 0.1, &texture_names[2]),
            (&texture_names[4], 0.1, &texture_names[5]),
            (&texture_names[5], 0.1, &texture_names[4]),
            (&texture_names[7], 1.0, &texture_names[7]),
        ];

        let mut textures = HashMap::new();
        for i in 0..texture_infos.len() {
            let texture =
                TEXTURES()[texture_infos[i].0].clone();
            textures.insert(
                texture_infos[i].0.clone(),
                (texture, texture_infos[i].1, texture_infos[i].2.clone()), 
            );
        }
        self.textures = textures;

        self.current_texture = "Stand1".into();

        let collision_boxes = vec![
            (
                State::Stand,
                Vector2::new(-0.4 * self.pic_size.x, 0.8 * self.pic_size.y),
                Vector2::new( 0.4 * self.pic_size.x, 0.0),
            ),
            (
                State::Crouch,
                Vector2::new(-0.4 * self.pic_size.x, 0.4 * self.pic_size.y),
                Vector2::new( 0.4 * self.pic_size.x, 0.0),
            ),
            (
                State::Jump,
                Vector2::new(-0.4 * self.pic_size.x, 0.8 * self.pic_size.y),
                Vector2::new( 0.4 * self.pic_size.x, 0.0),
            ),
            (
                State::Attack,
                Vector2::new(-0.4 * self.pic_size.x, 0.4 * self.pic_size.y),
                Vector2::new( 0.4 * self.pic_size.x, 0.0),
            ),
            (
                State::Dash,
                Vector2::new(-0.4 * self.pic_size.x, 0.4 * self.pic_size.y),
                Vector2::new( 0.4 * self.pic_size.x, 0.0),
            ),
            (
                State::Fail,
                Vector2::new(-0.4 * self.pic_size.x, 0.8 * self.pic_size.y),
                Vector2::new( 0.4 * self.pic_size.x, 0.0),
            ),
        ];
        for (state, lu, rd) in collision_boxes.iter() {
            self.collision_boxes.insert(*state, (*lu, *rd));
        }
    }

    pub fn fail(&mut self) {
        self.current_state = State::Fail;
        self.state_timer = self.current_state.lasting_time();

        self.current_texture = "Fail".into();
        self.animation_timer = f64::INFINITY;
    }

    pub fn hurt(&mut self) {
        self.current_health -= 1;

        self.to_play_hurt_animation = true;
        self.current_texture = "Fail".into();
        self.animation_timer = 0.5;
    }

    pub fn handle_input(
        &mut self,
        keyboard_state: &KeyboardState,
    ) {
        match self.current_state {
            State::Stand => {
                if keyboard_state.is_key_pressed(Key::A) &&
                keyboard_state.is_key_pressed(Key::D) {
                    self.velocity.x = 0.0;
                } else if keyboard_state.is_key_pressed(Key::A) {
                    self.velocity.x = -400.0;
                } else if keyboard_state.is_key_pressed(Key::D) {
                    self.velocity.x = 400.0;
                } else {
                    self.velocity.x = 0.0;
                }

                if keyboard_state.is_key_pressed(Key::W) &&
                !self.already_jump {
                    self.already_jump = true;

                    self.current_state = State::Jump;
                    self.state_timer = State::Jump.lasting_time();
                    self.velocity.y = 500.0;
                } else if keyboard_state.is_key_pressed(Key::S) {
                    self.current_state = State::Crouch;
                    self.state_timer = State::Crouch.lasting_time();

                    self.velocity.x = get_sign(self.velocity.x) * 100.0;

                    self.current_texture = "Crouch1".into();
                    self.animation_timer = self.textures[&self.current_texture].1;
                }

                if keyboard_state.is_key_pressed(Key::J) {
                    self.more_jump = false;

                    self.current_state = State::Attack;
                    self.state_timer = State::Attack.lasting_time();

                    self.current_texture = "Attack1".into();
                    self.animation_timer = self.textures[&self.current_texture].1;
                }

                if keyboard_state.is_key_pressed(Key::K) &&
                self.current_energy > 0 {
                    self.current_energy -= 1;

                    self.is_invincible = true;
                    self.invincible_timer = 0.5;

                    self.current_state = State::Dash;
                    self.state_timer = State::Dash.lasting_time();

                    self.current_texture = "Crouch1".into();
                    self.animation_timer = self.textures[&self.current_texture].1;

                    self.velocity.x = get_sign(self.velocity.x) * 1200.0;
                    self.velocity.y = 0.0;
                }
            }
            State::Crouch => {
                if keyboard_state.is_key_pressed(Key::A) &&
                keyboard_state.is_key_pressed(Key::D) {
                    self.velocity.x = 0.0;
                } else if keyboard_state.is_key_pressed(Key::A) {
                    self.velocity.x = -100.0;
                } else if keyboard_state.is_key_pressed(Key::D) {
                    self.velocity.x = 100.0;
                } else {
                    self.velocity.x = 0.0;
                }

                if keyboard_state.is_key_pressed(Key::W) &&
                !self.already_jump {
                    self.already_jump = true;

                    self.current_state = State::Jump;
                    self.state_timer = State::Jump.lasting_time();
                    self.velocity.y = 500.0;
                }

                if !keyboard_state.is_key_pressed(Key::S) {
                    self.current_state = State::Stand;
                    self.state_timer = State::Stand.lasting_time();

                    self.velocity.x = get_sign(self.velocity.x) * 400.0;

                    self.current_texture = "Stand1".into();
                    self.animation_timer = self.textures[&self.current_texture].1;
                }

                if keyboard_state.is_key_pressed(Key::J) {
                    self.more_jump = false;

                    self.current_state = State::Attack;
                    self.state_timer = State::Attack.lasting_time();

                    self.current_texture = "Attack1".into();
                    self.animation_timer = self.textures[&self.current_texture].1;
                }

                if keyboard_state.is_key_pressed(Key::K) &&
                self.current_energy > 0 {
                    self.current_energy -= 1;

                    self.is_invincible = true;
                    self.invincible_timer = 0.5;

                    self.current_state = State::Dash;
                    self.state_timer = State::Dash.lasting_time();

                    self.current_texture = "Crouch1".into();
                    self.animation_timer = self.textures[&self.current_texture].1;

                    self.velocity.x = get_sign(self.velocity.x) * 1200.0;
                    self.velocity.y = 0.0;
                }
            }
            State::Jump => {
                if keyboard_state.is_key_pressed(Key::A) &&
                keyboard_state.is_key_pressed(Key::D) {
                    self.velocity.x = 0.0;
                } else if keyboard_state.is_key_pressed(Key::A) {
                    self.velocity.x = -400.0;
                } else if keyboard_state.is_key_pressed(Key::D) {
                    self.velocity.x = 400.0;
                } else {
                    self.velocity.x = 0.0;
                }

                if keyboard_state.is_key_pressed(Key::W) {
                    if self.more_jump && self.jump_timer > 0.0 {
                        self.velocity.y = 500.0;
                    }
                } else {
                    self.more_jump = false;
                }

                if keyboard_state.is_key_pressed(Key::J) {
                    self.more_jump = false;

                    self.current_state = State::Attack;
                    self.state_timer = State::Attack.lasting_time();

                    self.current_texture = "Attack1".into();
                    self.animation_timer = self.textures[&self.current_texture].1;
                }

                if keyboard_state.is_key_pressed(Key::K) &&
                self.current_energy > 0 {
                    self.current_energy -= 1;

                    self.is_invincible = true;
                    self.invincible_timer = 0.5;

                    self.current_state = State::Dash;
                    self.state_timer = State::Dash.lasting_time();

                    self.current_texture = "Crouch1".into();
                    self.animation_timer = self.textures[&self.current_texture].1;

                    self.velocity.x = get_sign(self.velocity.x) * 1200.0;
                    self.velocity.y = 0.0;
                }
            }
            State::Dash => {
                // A, D unavailable
                // J unavailable
                // all unavailable
            }
            State::Attack => {
                if keyboard_state.is_key_pressed(Key::A) &&
                keyboard_state.is_key_pressed(Key::D) {
                    self.velocity.x = 0.0;
                } else if keyboard_state.is_key_pressed(Key::A) {
                    self.velocity.x = -100.0;
                } else if keyboard_state.is_key_pressed(Key::D) {
                    self.velocity.x = 100.0;
                } else {
                    self.velocity.x = 0.0;
                }

                // cancel! cancel! soul5!
                if keyboard_state.is_key_pressed(Key::K) &&
                self.current_energy > 0 {
                    self.current_energy -= 1;

                    self.is_invincible = true;
                    self.invincible_timer = 0.5;

                    self.current_state = State::Dash;
                    self.state_timer = State::Dash.lasting_time();

                    self.current_texture = "Crouch1".into();
                    self.animation_timer = self.textures[&self.current_texture].1;

                    self.velocity.x = get_sign(self.velocity.x) * 1200.0;
                    self.velocity.y = 0.0;
                }
            }
            State::Fail => {
                // all input will not be accepted
            }
        }
    }

    /// the return value represents how many fireballs shall be created
    #[must_use]
    pub fn update(
        &mut self,
        delta_time: f64,
    ) -> Option<i64> {
        let mut new_fireballs = None;

        if self.is_invincible == true {
            self.invincible_timer -= delta_time;
            if self.invincible_timer <= 0.0 {
                self.is_invincible = false;
            }
        }

        self.energy_recovery_timer -= delta_time;
        if self.energy_recovery_timer <= 0.0 {
            self.energy_recovery_timer = 5.0;
            if self.current_energy < self.energy_max {
                self.current_energy += 1;
            }
        }

        self.health_recovery_timer -= delta_time;
        if self.health_recovery_timer <= 0.0 {
            self.health_recovery_timer = 60.0;
            if self.current_health < self.health_max {
                self.current_health += 1;
            }
        }

        self.regular_position_update(delta_time);

        if self.position.y <= 0.0 {
            self.already_jump = false;
            self.more_jump = true;
            self.jump_timer = 0.3;

            self.position.y = 0.0;
            self.velocity.y = 0.0;
        }

        // detect collision with obstructions here

        if self.state_timer.is_finite() {
            self.state_timer -= delta_time;
        }

        self.animation_timer -= delta_time;
        if self.animation_timer <= 0.0 {
            if self.to_play_hurt_animation {
                self.current_texture = match self.current_state {
                    State::Crouch => "Crouch1".into(),
                    _ => "Stand1".into(),
                };
                self.animation_timer = 
                    self.textures[&self.current_texture].1;
                self.to_play_hurt_animation = false;
            } else {
                self.current_texture =
                    self.textures[&self.current_texture].2.clone();
                self.animation_timer =
                    self.textures[&self.current_texture].1;
            }

        }

        match self.current_state {
            State::Stand => {

            }
            State::Crouch => {

            }
            State::Jump => {
                if self.more_jump {
                    if self.jump_timer > 0.0 {
                        self.jump_timer -= delta_time;
                    } else {
                        self.more_jump = false;
                    }
                } else {
                    if -500.0 < self.velocity.y {
                        self.velocity.y -= 50.0;
                    } else {
                        self.velocity.y = -500.0;
                    }
                }


                if self.position.y <= 0.0 {
                    self.current_state = State::Stand;
                    self.state_timer = State::Stand.lasting_time();
                }
            }
            State::Attack => {
                if -500.0 < self.velocity.y {
                    self.velocity.y -= 50.0;
                } else {
                    self.velocity.y = -500.0;
                }

                if self.state_timer <= 0.1 && !self.already_spit {
                    new_fireballs = Some(1);
                    self.already_spit = true;
                }


                if self.state_timer <= 0.0 {
                    self.already_spit = false;


                    if self.position.y <= 0.0 {
                        self.current_state = State::Stand;
                        self.state_timer = State::Stand.lasting_time();
                    } else {
                        self.current_state = State::Jump;
                        self.state_timer = State::Jump.lasting_time();
                    }
                    self.current_texture = "Stand1".into();
                    self.animation_timer = self.textures[&self.current_texture].1;
                }
            }
            State::Dash => {
                if self.state_timer <= 0.0 {
                    self.already_jump = true;
                    self.more_jump = false;

                    self.current_state = State::Jump;
                    self.state_timer = State::Jump.lasting_time();

                    self.current_texture = "Stand1".into();
                    self.animation_timer = self.textures[&self.current_texture].1;

                    self.velocity.x = get_sign(self.velocity.x) * 400.0;
                }
            }
            State::Fail => {

            }
        }

        new_fireballs
    }

    fn regular_position_update(
        &mut self,
        delta_time: f64,
    ) {
        self.position = self.position + self.velocity * delta_time;
        if self.position.x + self.pic_size.x * 0.5 > 500.0 {
            self.position.x = 500.0 - self.pic_size.x * 0.5;
        } else if self.position.x - self.pic_size.x * 0.5 < -500.0 {
            self.position.x = -500.0 + self.pic_size.x * 0.5;
        }

        if self.position.y > 300.0 {
            self.position.y = 300.0;
        } else if self.position.y < -300.0 {
            self.position.y = -300.0;
        }
    }

    pub fn draw(
        &self,
        renderer: &mut Renderer,
        camera: &Camera,
    ) {
        let leftup_world =
            Vector2::new(
                self.position.x - self.pic_size.x * 0.5,
                self.position.y + self.pic_size.y,
            );
        let rightdown_world =
            Vector2::new(
                self.position.x + self.pic_size.x * 0.5,
                self.position.y,
            );

        let leftup_screen = world_to_screen(
            &leftup_world,
            camera,
            &Vector2::new(WIDTH as _, HEIGHT as _)
        );
        let rightdown_screen = world_to_screen(
            &rightdown_world,
            camera,
            &Vector2::new(WIDTH as _, HEIGHT as _)
        );

        // println!("current_texture: {}", self.current_texture);
        renderer.draw_texture(
            &leftup_screen,
            &rightdown_screen,
            &self.textures[&self.current_texture].0,
        );
    }

    pub fn draw_collision_box(
        &self,
        renderer: &mut Renderer,
        camera: &Camera,
    ) {
        let screen_size = Vector2::new(WIDTH as f64, HEIGHT as f64);
        let (lu_w, rd_w) = (
            self.get_current_collision_box().0 + self.position,
            self.get_current_collision_box().1 + self.position,
        );
        let lu_s = world_to_screen(&lu_w, camera, &screen_size);
        let rd_s = world_to_screen(&rd_w, camera, &screen_size);

        renderer.draw_rect(
            &lu_s,
            &rd_s,
            Rgba([255, 0, 0, 255]),
        );
    }

    pub fn get_state(&self) -> State {
        self.current_state
    }

    pub fn get_position(&self) -> Vector2<f64> {
        self.position
    }

    pub fn get_velocity(&self) -> Vector2<f64> {
        self.velocity
    }

    pub fn get_size(&self) -> Vector2<f64> {
        self.pic_size
    }

    pub fn get_current_health(&self) -> i64 {
        self.current_health
    }

    pub fn set_current_health(
        &mut self,
        new_health: i64,
    ) {
        self.current_health = new_health;
    }

    pub fn get_current_energy(&self) -> i64 {
        self.current_energy
    }

    pub fn set_current_energy(
        &mut self,
        new_energy: i64,
    ) {
        self.current_energy = new_energy;
    }

    pub fn get_current_collision_box(
        &self
    ) -> (Vector2<f64>, Vector2<f64>) {
        self.collision_boxes[&self.current_state]
    }

    pub fn is_invincible(&self) -> bool {
        self.is_invincible
    }

    pub fn set_invincible(
        &mut self,
        invincible_time: f64,
    ) {
        self.is_invincible = true;
        self.invincible_timer = invincible_time;
    }
}

pub struct Fireball {
    pic_size: Vector2<f64>,
    position: Vector2<f64>,
    velocity: Vector2<f64>,

    texture: Texture,

    collision_box: (Vector2<f64>, Vector2<f64>),
}

impl Fireball {
    pub fn gen(
        position: Vector2<f64>,
        velocity: Vector2<f64>,
    ) -> Self {
        let texture = TEXTURES()["Fireball"].clone();

        let pic_size = Vector2::new(80.0, 80.0);
        Self {
            pic_size,
            position,
            velocity,

            texture,

            collision_box: (
                Vector2::new(-0.5 * pic_size.x, pic_size.y),
                Vector2::new( 0.5 * pic_size.x, 0.0),
            )
        }
    }

    pub fn update(
        &mut self,
        delta_time: f64,
    ) {
        self.position = self.position + self.velocity * delta_time;
    }

    pub fn draw(
        &self,
        renderer: &mut Renderer,
        camera: &Camera,
    ) {
        let leftup_world =
            Vector2::new(
                self.position.x - self.pic_size.x * 0.5,
                self.position.y + self.pic_size.y,
            );
        let rightdown_world =
            Vector2::new(
                self.position.x + self.pic_size.x * 0.5,
                self.position.y,
            );

        let leftup_screen = world_to_screen(
            &leftup_world,
            camera,
            &Vector2::new(WIDTH as _, HEIGHT as _)
        );
        let rightdown_screen = world_to_screen(
            &rightdown_world,
            camera,
            &Vector2::new(WIDTH as _, HEIGHT as _)
        );

        renderer.draw_texture(
            &leftup_screen,
            &rightdown_screen,
            &self.texture,
        );
    }

    pub fn draw_collision_box(
        &self,
        renderer: &mut Renderer,
        camera: &Camera,
    ) {
        let screen_size = Vector2::new(WIDTH as f64, HEIGHT as f64);
        let (lu_w, rd_w) = (
            self.get_current_collision_box().0 + self.position,
            self.get_current_collision_box().1 + self.position,
        );
        let lu_s = world_to_screen(&lu_w, camera, &screen_size);
        let rd_s = world_to_screen(&rd_w, camera, &screen_size);

        renderer.draw_rect(
            &lu_s,
            &rd_s,
            Rgba([255, 0, 0, 255]),
        );
    }

    pub fn get_position(&self) -> Vector2<f64> {
        self.position
    }

    pub fn get_velocity(&self) -> Vector2<f64> {
        self.velocity
    }

    pub fn get_current_collision_box(
        &self
    ) -> (Vector2<f64>, Vector2<f64>) {
        self.collision_box
    }
}